#include "Replication.h"

Replication::Replication(short port) :
	sendBuffer(new char[WBuffLen]()),
	recvBuffer(new char[RBuffLen]()),
	mt(rd()),
	dis(0x0f, 0xffff),
	roomId(0),
	fromAddrLength(sizeof(sockaddr)),
	syncTimer(Clock() + FPS)
{
	sockaddr_in hostAddr;
	hostAddr.sin_family = AF_INET;
	hostAddr.sin_port = htons(port);
	hostAddr.sin_addr.s_addr = INADDR_ANY;
#ifdef _WIN32
	WSAData wsadata;
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) == -1)
		perror("ERROE ON WSAStartup!\n");
#endif
	socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (socket_fd == -1)
		perror("ERROE ON CREATEING SOCKET!\n");
	int err = bind(socket_fd, (sockaddr *)&hostAddr, sizeof(hostAddr));
	if (err == -1)
		perror("ERROE ON BINDING SOCKET!\n");
#ifndef _WIN32
	fcntl(socket_fd, F_SETFL, (fcntl(socket_fd, F_GETFL) | O_NONBLOCK));
#else
	u_long io = 1;
	ioctlsocket(socket_fd, FIONBIO, &io);
#endif
}

Replication::~Replication()
{
#ifndef _WIN32
	close(socket_fd);
#else
	closesocket(socket_fd);
#endif
	for (auto iter = EntryPlayers.begin(); iter != EntryPlayers.end(); iter++)
		delete iter->second;

	for (auto iter = SyncPlayerControlls.begin(); iter != SyncPlayerControlls.end(); iter++)
	{
		for (auto iter1 = iter->second->playersInfo.begin(); iter1 != iter->second->playersInfo.end(); iter1++)
		{
			delete ((RUDP *)iter1->second.RUDPSes);
			delete ((sockaddr *)iter1->second.Addr);
		}
		delete iter->second;
	}

	while (!RudpRecvQueue.empty())
	{
		RUDP::releaseFragment((RUDP::Fragment *)RudpRecvQueue.front());
		RudpRecvQueue.pop();
	}

	delete[] recvBuffer;
	delete[] sendBuffer;
}

void Replication::Run()
{
	int receivedSize = recvfrom(socket_fd, recvBuffer, RBuffLen, 0, &from, &fromAddrLength);
	if (receivedSize > 0)
	{
		uint8_t MSGType;
		char* ptr = recvBuffer;
		std::map<uint32_t, RUDPINFO>::iterator rudp_iterator;

		ptr = RepProtocol::DeserializeMSGType(ptr, MSGType);

		switch (MSGType)
		{
		case Entry_MSG:
			OnEntry(ptr, &from);
			break;
		default:
			rudp_iterator = RudpSession.find(HashCode(&from));
			if (rudp_iterator != RudpSession.end())
				rudp_iterator->second.RUDPSession->Input(recvBuffer, receivedSize, inet_ntoa(((sockaddr_in*)&from)->sin_addr));
			break;
		}
	}
	else if (SyncPlayerControlls.size() < 2) {
		// if team player max then the size
		// no delay
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}

	MatchUpdate();
	RudpUpdate();
	Synchronization();
}

void Replication::OnEntry(char *ptr, sockaddr *fromAddr)
{
	// create players and initialize player information
	auto hash = HashCode(fromAddr);
	PlayerMatching PlayerInfo;
	RepProtocol::DeserializeEntryMSG(ptr, PlayerInfo);

	if (RudpSession.find(hash) != RudpSession.end())
		return;

	auto Room = EntryPlayers.find(PlayerInfo.TeamType);
	if (Room != EntryPlayers.end())
	{
		auto matchingPlayer = Room->second->find(hash);
		if (matchingPlayer != Room->second->end())
		{
			matchingPlayer->second.ts = this->Clock() + MaxMatchTime;
			return;
		}

		InitializeMatchingPlayer(PlayerInfo, fromAddr);
		((*Room->second)[hash]) = PlayerInfo;
	}
	else
	{
		InitializeMatchingPlayer(PlayerInfo, fromAddr);
		EntryPlayers[PlayerInfo.TeamType] = [&] {
			auto PlayerEntities = new std::map<uint32_t, PlayerMatching>();
			((*PlayerEntities)[hash]) = PlayerInfo;
			return PlayerEntities;
		}();
		Room = EntryPlayers.begin();
	}

	switch (PlayerInfo.TeamType)
	{
	case 0:
		if (TeamMode::Solo == Room->second->size())
			EnterRoom(Room);
		break;
	case 1:
		if (TeamMode::Dou == Room->second->size())
			EnterRoom(Room);
		break;
	case 2:
		if (TeamMode::Squad == Room->second->size())
			EnterRoom(Room);
		break;
	default:
		break;
	}
}

void Replication::OnReady(char *ptr, sockaddr *fromAddr)
{
	PlayerReady ready;
	auto hash = HashCode(fromAddr);
	RepProtocol::DeserializeReadyMSG(ptr, ready);

	auto room = SyncPlayerControlls.find(ready.RoomId);
	if (room != SyncPlayerControlls.end())
	{
		auto playerControll = room->second->playersInfo.find(hash);
		if (playerControll != room->second->playersInfo.end())
		{
			if (playerControll->second.playerId == ready.PlayerID)
				playerControll->second.ready = true;
		}
	}
}

void Replication::OnControll(char *ptr, sockaddr *fromAddr)
{
	PlayerInput playerInput;
	auto hash = HashCode(fromAddr);
	RepProtocol::DeserializeControllsMSG(ptr, playerInput);

	auto room = SyncPlayerControlls.find(playerInput.RoomId);
	if (room != SyncPlayerControlls.end())
	{
		auto playerControll = room->second->playerControll.find(hash);
		if (playerControll != room->second->playerControll.end())
		{
			playerControll->second.push_back(playerInput.Controll);
			room->second->ts = this->Clock() + MaxWaitInputTime;
		}
	}
}

void Replication::RudpUpdate()
{
	for (auto rudpIterator = RudpSession.begin(); rudpIterator != RudpSession.end();)
	{
		uint8_t MSGType;
		if (rudpIterator->second.RUDPSession->Recv(RudpRecvQueue))
		{
			while (!RudpRecvQueue.empty())
			{
				auto Data = RudpRecvQueue.front();
				char *ptr = Data->buf;

				ptr = RepProtocol::DeserializeMSGType(ptr, MSGType);

				switch (MSGType)
				{
				case Ready2Play_MSG:
					OnReady(ptr, rudpIterator->second.endpoint);
					break;
				case Controll_MSG:
					OnControll(ptr, rudpIterator->second.endpoint);
					break;
				default:
					break;
				}

				RudpRecvQueue.pop();
				RUDP::releaseFragment((RUDP::Fragment *)Data);
			}
		}

		// if pennding sned data size is max then the max pending data
		// assume the connection is shut down
		if (rudpIterator->second.RUDPSession->PendingSendCount() >= MaxPendingSendCount)
		{
			rudpIterator->second.RUDPSession->Disable();
			rudpIterator = RudpSession.erase(rudpIterator);
			continue;
		}

		rudpIterator->second.RUDPSession->Update();
		rudpIterator++;
	}
}

void Replication::MatchUpdate()
{
	for (auto teamType_iter = EntryPlayers.begin(); teamType_iter != EntryPlayers.end(); teamType_iter++)
	{
		for (auto matchingPlayer_iter = teamType_iter->second->begin(); matchingPlayer_iter != teamType_iter->second->end();)
		{
			int64_t ts = this->Clock() - matchingPlayer_iter->second.ts;
			if (ts >= 0)
			{
				matchingPlayer_iter = teamType_iter->second->erase(matchingPlayer_iter);
			}
			else
				matchingPlayer_iter++;
		}
	}
}

void Replication::Synchronization()
{
	int ts = this->Clock() - syncTimer;
	if (ts >= 0)
	{
		for (auto room_iter = SyncPlayerControlls.begin(); room_iter != SyncPlayerControlls.end();)
		{
			PlayerInput p;
			bool isReady = true;
			char *ptr = sendBuffer;
			ptr = RepProtocol::SerializeMSGType(ptr, Controll_MSG);

			std::map<uint32_t, PlayerInfo>::iterator playerInfo_iter;
			std::map<uint32_t, std::list<unsigned short>>::iterator playerInput_iter;

			for (playerInput_iter = room_iter->second->playerControll.begin(), playerInfo_iter = room_iter->second->playersInfo.begin(); 
				playerInput_iter != room_iter->second->playerControll.end(); playerInput_iter++, playerInfo_iter++)
			{
				if (!playerInfo_iter->second.ready)
				{
					isReady = false;
					break;
				}

				p.PlayerID = playerInfo_iter->second.playerId;
				p.Controll = playerInput_iter->second.front();
				ptr = RepProtocol::SerializeControllsMSG(p, ptr);
			}

			if (isReady)
			{
				for (playerInfo_iter = room_iter->second->playersInfo.begin(), playerInput_iter = room_iter->second->playerControll.begin();
					playerInfo_iter != room_iter->second->playersInfo.end(); playerInfo_iter++, playerInput_iter++)
				{
					((RUDP *)playerInfo_iter->second.RUDPSes)->Send(sendBuffer, ptr - sendBuffer);
					if (playerInput_iter->second.size() > 1) {
						playerInput_iter->second.pop_front();
					}
				}
			}

			ts = this->Clock() - room_iter->second->ts;
			if (ts >= 0)
			{
				RemoveRoom(room_iter);
				room_iter = SyncPlayerControlls.erase(room_iter);
			}
			else {
				room_iter++;
			}
		}

		syncTimer = this->Clock() + FPS;
	}
}

void Replication::RemoveRoom(std::map<uint16_t, PlayerController *>::iterator &iter)
{
	for (auto playerInfo_iter = iter->second->playersInfo.begin(); playerInfo_iter != iter->second->playersInfo.end(); playerInfo_iter++)
	{
		iter->second->playerControll.clear();
		delete ((RUDP *)playerInfo_iter->second.RUDPSes);
		delete ((sockaddr *)playerInfo_iter->second.Addr);
		RudpSession.erase(playerInfo_iter->first);
	}
	delete iter->second;
}

void Replication::InitializeMatchingPlayer(PlayerMatching &player, sockaddr *from_addr)
{
	sockaddr *endpoint = new sockaddr;
	*endpoint = *from_addr;

	RUDP *rudpSession = new RUDP(100, 2, 512, RUDP_MSG);
	rudpSession->SetOutput(std::bind(sendto, socket_fd,
									 std::placeholders::_1, std::placeholders::_2,
									 0, (sockaddr *)endpoint, sizeof(sockaddr)));

	player.Addr = endpoint;
	player.RUDPSes = rudpSession;
	player.ts = this->Clock() + MaxMatchTime;
}

void Replication::EnterRoom(std::map<uint8_t, std::map<uint32_t, PlayerMatching> *>::iterator &matchedRoom)
{
	while (true)
	{
		roomId = RandomID();
		auto exist = SyncPlayerControlls.find(roomId);
		if (exist == SyncPlayerControlls.end())
			break;
	}

	char *ptr = sendBuffer;
	char *ptr1 = recvBuffer;
	uint8_t playerID = 0;
	PlayerController *playerController = new PlayerController;
	RUDP *rudpSession;
	ptr = RepProtocol::SerializeMSGType(ptr, Entry_MSG);

	for (auto iter = matchedRoom->second->begin(); iter != matchedRoom->second->end(); iter++)
	{
		iter->second.RoomId = roomId;
		iter->second.PlayerID = ++playerID;
		ptr = RepProtocol::SerializeEntryMSG(iter->second, ptr);

		rudpSession = (RUDP *)iter->second.RUDPSes;
		RudpSession[iter->first] = { rudpSession, (sockaddr*)iter->second.Addr };

		// no controlls
		playerController->playerControll[iter->first].push_back(0x0);
		// player infomations
		playerController->playersInfo[iter->first] = { iter->second.PlayerID, false, rudpSession, iter->second.Addr };
		// wait other player time out
		playerController->ts = this->Clock() + MaxWaitPlayerEntryTime;

		ptr1 = RepProtocol::SerializeMSGType(ptr1, GainControlID_MSG);
		ptr1 = RepProtocol::SerializeGainControlIDMSG(iter->second.PlayerID, roomId, ptr1);
		rudpSession->Send(recvBuffer, ptr1 - recvBuffer);

		ptr1 = recvBuffer;
	}

	for (auto iter = matchedRoom->second->begin(); iter != matchedRoom->second->end(); iter++)
		((RUDP *)iter->second.RUDPSes)->Send(sendBuffer, ptr - sendBuffer);

	SyncPlayerControlls[roomId] = playerController;
	delete matchedRoom->second;
	EntryPlayers.erase(matchedRoom);
}

uint16_t Replication::RandomID()
{
	return dis(mt);
}

uint32_t Replication::HashCode(sockaddr *addr)
{
	return ((sockaddr_in *)addr)->sin_addr.s_addr / ((sockaddr_in *)addr)->sin_port;
}

uint64_t Replication::Clock()
{
	return std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()).time_since_epoch().count();
}